package com.altekis.kanbankaze.client;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import com.allen_sauer.gwt.dnd.client.drop.AbsolutePositionDropController;
import com.altekis.kanbankaze.shared.DemoData;
import com.altekis.kanbankaze.shared.KanbanProperties;
import com.altekis.kanbankaze.shared.Stage;
import com.altekis.kanbankaze.shared.Task;
import com.altekis.kanbankaze.shared.UserAccount;
import com.altekis.kanbankaze.shared.UserStory;
import com.altekis.kanbankaze.shared.events.CardEditedEvent;
import com.altekis.kanbankaze.shared.events.CardEditedEventHandler;
import com.altekis.kanbankaze.shared.events.CardUpdatedRemoteEvent;
import com.altekis.kanbankaze.shared.events.CardUpdatedRemoteEventHandler;
import com.altekis.kanbankaze.shared.events.LoginCompleteEvent;
import com.altekis.kanbankaze.shared.events.TasksLoadedEvent;
import com.altekis.kanbankaze.shared.events.TasksLoadedEventHandler;
import com.altekis.kanbankaze.shared.events.UserStoriesLoadedEvent;
import com.altekis.kanbankaze.shared.events.UserStoriesLoadedEventHandler;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.BodyElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.BorderStyle;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.HasLoadHandlers;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.NamedFrame;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * Board presenter
 */
@Singleton
public class KanbanPresenter {

	interface Display extends HasLoadHandlers {
		HasClickHandlers getLoginButton();

		HasValue<String> getLoginTextBox();
		Panel getPanel();
		Panel getToolbar();
	}

	protected final BusyIndicatorPresenter busyIndicatorPresenter;
	protected BusyIndicatorView busyIndicatorView = new BusyIndicatorView();

	protected Display display;
	private CardDragController dragController;

	protected final EventBus eventBus;

	private KanbanProperties kanbanProperties;

	DialogBox loginDialogBox = null;

	private List<Stage> stages;

	private final FlexTable table = new FlexTable();
	/**
	 * Graphical elements - via Presenters
	 */
	protected List<TaskCardPresenter> taskCardPresenters;
	private List<Task> tasks = new LinkedList<Task>();

	protected final TaskServiceAsync taskService;
	protected final UserAccountServiceAsync userAccountService;
	// Data collections
	private List<UserStory> userStories = new LinkedList<UserStory>();
	protected List<UserStoryCardPresenter> userStoryCardPresenters;

	protected float widthToFontSize = -1f;
	protected final AvatarSelectionPresenter avatarSelectionPresenter;
	protected final AvatarSelectionView avatarSelectionView = new AvatarSelectionView();

	@Inject
	public KanbanPresenter(TaskServiceAsync taskService,
			UserAccountServiceAsync userAccountService, EventBus eventBus,
			BusyIndicatorPresenter busyIndicatorPresenter, AvatarSelectionPresenter avatarSelectionPresenter) {
		super();
		this.taskService = taskService;
		this.userAccountService = userAccountService;
		this.eventBus = eventBus;
		this.busyIndicatorPresenter = busyIndicatorPresenter;
		this.avatarSelectionPresenter = avatarSelectionPresenter;
		
		// TODO Get properties
		kanbanProperties = new KanbanProperties();

		// Events attachment
		// Add Event Bus handlers - CARD EDITION
		eventBus.addHandler(CardEditedEvent.TYPE, new CardEditedEventHandler() {
			@Override
			public void onCardEdited(CardEditedEvent cardEditedEvent) {
				// Iterate through task cards, looking for the changed
				// one
				for (TaskCardPresenter taskCardPresenter : taskCardPresenters) {
					if (taskCardPresenter.task.equals(cardEditedEvent.task)) {
						taskCardPresenter.task = cardEditedEvent.task;
						taskCardPresenter.refresh();
						break; // found
						// TO DO - Change iteration? Maybe direct
						// access?
					}
				}
			}
		});
		// Add Event Bus handlers - (REMOTE) CARD UPDATED
		eventBus.addHandler(CardUpdatedRemoteEvent.TYPE,
				new CardUpdatedRemoteEventHandler() {
					@Override
					public void onCardUpdatedRemote(
							CardUpdatedRemoteEvent cardUpdatedRemoteEvent) {
						// Recorremos los StageView, buscando el que
						GWT.log("onCardUpdatedRemote");
						for (TaskCardPresenter taskCardPresenter : taskCardPresenters) {
							if (taskCardPresenter.task.getId() == cardUpdatedRemoteEvent.taskDTO
									.getId()) {
								// Matching card found!
								GWT.log("matching card found");
								// TO DO - Update presenter, view,
								// etc...
								// Something like...

								// StageView w = (StageView)
								// table.getWidget(task.getPriority()+1,
								// task.getStage());
								// w.add(taskCardView);

								break;
								// TO DO - Change iteration? Maybe
								// direct access?
							}
						}
					}
				});

		// Add Event Bus handlers - USER STORIES LOADED
		eventBus.addHandler(UserStoriesLoadedEvent.TYPE,
				new UserStoriesLoadedEventHandler() {
					@Override
					public void onUserStoriesLoaded(
							UserStoriesLoadedEvent userStoriesLoadedEvent) {
						doUserStoriesLoaded(userStoriesLoadedEvent.userStories);
					}
				});
		// Add Event Bus handlers - TASKS LOADED
		eventBus.addHandler(TasksLoadedEvent.TYPE,
				new TasksLoadedEventHandler() {
					@Override
					public void onTasksLoaded(TasksLoadedEvent tasksLoadedEvent) {
						doTasksLoaded(tasksLoadedEvent.tasks);
					}
				});

	}

	void bindDisplay(Display display) {
		this.display = display;
		
		this.dragController = new CardDragController(
				(AbsolutePanel) display.getPanel(), false);
		dragController.setBehaviorDragStartSensitivity(2);

		display.getLoginButton().addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				getLoggedInUser();
			}
		});

		Window.addResizeHandler(new ResizeHandler() {
			@Override
			public void onResize(ResizeEvent event) {
				computeFontSize();
			}
		});

		Scheduler.get().scheduleFinally(new ScheduledCommand() {
			@Override
			public void execute() {
				initWidthToFontSize();
				computeFontSize();
			}
		});
	}

	/**
	 * Computes the document's fontSize for the Kanban board to fit the window
	 */
	private void computeFontSize() {
		final float clientWidth = Window.getClientWidth();
		final float newEmWidth = clientWidth / 130f;
		final float newFontSize = getWidthToFontSize() * newEmWidth;
		final Document document = Document.get();
		final BodyElement body = document.getBody();
		final Style style = body.getStyle();
		style.setFontSize(newFontSize, Unit.EM);
	}

	private void createChannelForCurrentUser() {
		if (Kanbankaze.loggedUser != null) {
			userAccountService
					.createChannelForCurrentUser(new AsyncCallback<UserAccount>() {
						@Override
						public void onFailure(Throwable caught) {
							// TODO Handle exception
							caught.printStackTrace();
						}

						@Override
						public void onSuccess(UserAccount userAccount) {
							eventBus.fireEvent(new LoginCompleteEvent(
									userAccount));
						}
					});

		}
	}

	/**
	 * 
	 */
	private void showLoginPage() {
		// TODO convertir en algo más decente!!!!
		// Login start
		// Obtenemos la ruta... e intentamos un login
		userAccountService.createLoginURL("/UserLogged.html",
				new AsyncCallback<String>() {
					@Override
					public void onFailure(Throwable caught) {
						Window.alert(caught.getMessage());
					}

					@Override
					public void onSuccess(String loginUrl) {
						loginDialogBox = new DialogBox();
						loginDialogBox.getElement().getStyle().setZIndex(100); // Hay
																				// que
																				// poner
																				// un
																				// z-index
																				// grande,
																				// o
																				// queda
																				// debajo
																				// de
																				// las
																				// tarjetas
						loginDialogBox.setModal(true);
						loginDialogBox.setPixelSize(400, 400);

						NamedFrame nf = new NamedFrame("namedFrame");
						nf.setHeight("400px");
						nf.setWidth("400px");
						nf.setUrl(loginUrl);
						VerticalPanel panel = new VerticalPanel();
						panel.add(nf);
						Button b = new Button("Close");
						b.addClickHandler(new ClickHandler() {
							public void onClick(ClickEvent event) {
								doLoginComplete();
							}
						});
						panel.add(b);
						loginDialogBox.add(panel);
						loginDialogBox.center();
					}
				});
	}

	private void doLoginComplete() {
		// Llegamos aqui al cerrar el dialogbox de login
		// Comprobamos si estamos logados o no
		loginDialogBox.hide();
	}

	private void doTasksLoaded(List<Task> loadedTasks) {
		if (loadedTasks != null) {
			tasks.addAll(loadedTasks);
		}
		// If we have no data, add empty tasks (DEMO)
		if (tasks.isEmpty()) {
			tasks = DemoData.getDemoTasks();
		}

		// Add tasks to grid
		taskCardPresenters = new ArrayList<TaskCardPresenter>();
		for (Task task : tasks) {
			TaskCardPresenter taskCardPresenter = Injector.INSTANCE
					.getTaskCardPresenter();
			TaskCardView taskCardView = new TaskCardView(task);
			// dragController.makeDraggable(taskCardView,
			// taskCardView.getEditButton());
			dragController.makeDraggable(taskCardView);
			taskCardPresenter.bindDisplay(taskCardView);
			taskCardPresenter.showTask(task, kanbanProperties);
			// Add to presenters list
			taskCardPresenters.add(taskCardPresenter);
			TaskAvatarDropController dc = new TaskAvatarDropController(taskCardView, taskCardPresenter);
			avatarSelectionPresenter.getAvatarDragController().registerDropController(dc);
			StageView w = (StageView) table.getWidget(task.getPriority() + 1,
					task.getStage());
			w.add(taskCardView);
		}
	}

	private void doUserStoriesLoaded(List<UserStory> loadedUserStories) {
		if (loadedUserStories != null) {
			userStories.addAll(loadedUserStories);
		}
		// If we have no data, add empty user stories (DEMO)
		if (userStories.isEmpty()) {
			userStories = DemoData.getDemoUserStories();
		}

		// Add user stories to grid
		userStoryCardPresenters = new ArrayList<UserStoryCardPresenter>();
		for (UserStory userStory : userStories) {
			UserStoryCardPresenter userStoryCardPresenter = new UserStoryCardPresenter(
					taskService);
			UserStoryCardView taskCardView = new UserStoryCardView(userStory);
			// dragController.makeDraggable(taskCardView);
			userStoryCardPresenter.bindDisplay(taskCardView);
			userStoryCardPresenter.show(userStory, kanbanProperties);
			// Add to presenters list
			userStoryCardPresenters.add(userStoryCardPresenter);
			dragController.setBehaviorDragStartSensitivity(2); // Can't be 0, or
																// the OnClick
																// won't fire
			StageView w = (StageView) table.getWidget(
					userStory.getPriority() + 1, 0);
			w.add(taskCardView);
		}
	}

	private void findAllTasks() {
		new RPCCall<List<Task>>() {
			@Override
			protected void callService(AsyncCallback<List<Task>> cb) {
				taskService.findAll(cb);
			}

			@Override
			public void onFailure(Throwable caught) {
				Window.alert("An error occurred: " + caught.toString());
			}

			@Override
			public void onSuccess(List<Task> result) {
				eventBus.fireEvent(new TasksLoadedEvent(result));
			}
		}.retry(3, "Loading tasks");
	}

	private void findAllUserStories() {
		new RPCCall<List<UserStory>>() {
			@Override
			protected void callService(AsyncCallback<List<UserStory>> cb) {
				taskService.findAllUserStories(cb);
			}

			@Override
			public void onFailure(Throwable caught) {
				Window.alert("An error occurred: " + caught.toString());
			}

			@Override
			public void onSuccess(List<UserStory> result) {
				eventBus.fireEvent(new UserStoriesLoadedEvent(result));
			}
		}.retry(3, "Loading user stories");
	}

	/**
	 * @return the dragController
	 */
	public CardDragController getDragController() {
		return dragController;
	}

	private void getLoggedInUser() {
		new RPCCall<UserAccount>() {
			@Override
			protected void callService(AsyncCallback<UserAccount> cb) { // TODO
																		// UserAccount
																		// debe
																		// ser
																		// un
																		// DTO
				userAccountService.getCurrentUser(cb);
			}

			/*
			 * @Override public void onSuccess(UserAccount loggedInUserDTO) {
			 * //TODO UserAccount debe ser un DTO if (loggedInUserDTO == null) {
			 * // nobody is logged in showLoginView(); } else { // user is
			 * logged in setCurrentUser(loggedInUserDTO); goAfterLogin();
			 * eventBus.fireEvent(new LoginEvent(currentUser)); } }
			 */
			@Override
			public void onFailure(Throwable caught) {
				Window.alert("An error occurred: " + caught.toString());
			}

			@Override
			public void onSuccess(UserAccount loggedInUser) {
				if (loggedInUser != null) {
					Window.alert(loggedInUser.getName());
					Kanbankaze.loggedUser = loggedInUser;
					createChannelForCurrentUser();
				} else {
					showLoginPage();
				}
				// Kanbankaze.get().getEventBus().fireEvent(new
				// UserLoggedEvent(loggedInUser));
			}
		}.retry(3, "Checking logged user");
	}

	/**
	 * Must call {@link #initWidthToFontSize()} for this call to work
	 * 
	 * @return the widthToFontSize
	 * @throws IllegalStateException
	 *             If the relation of em width to font size has not been
	 *             initialized
	 */
	private float getWidthToFontSize() {
		if (widthToFontSize < 0f) {
			throw new IllegalStateException(
					"Relation not initialized. Call initWidthToFontSize before calling this method.");
		}
		return widthToFontSize;
	}

	/**
	 * Initializes the relation of the em width to the font size for the calls
	 * to {@link #getWidthToFontSize()} to work.
	 */
	private void initWidthToFontSize() {
		Document document = Document.get();
		DivElement div = document.createDivElement();
		Style style = div.getStyle();
		style.setWidth(1, Unit.EM);
		style.setBorderStyle(BorderStyle.NONE);
		style.setMargin(0, Unit.PX);
		style.setPadding(0, Unit.PX);
		style.setFontSize(1, Unit.EM);
		document.getBody().appendChild(div);
		int emWidth = div.getClientWidth();
		div.removeFromParent();
		float fontSize = Float.parseFloat(document.getBody().getStyle()
				.getFontSize().replace("em", ""));
		float widthToFontSize = fontSize / emWidth;
		setWidthToFontSize(widthToFontSize);
	}

	public void setPixelPosition(UIObject object, int x, int y) {
		final Style style = object.getElement().getStyle();
		style.setProperty("position", "absolute");
		style.setProperty("left", x / 10 + "em");
		style.setProperty("top", y / 10 + "em");
	}

	/**
	 * @param widthToFontSize
	 *            the widthToFontSize to set
	 */
	private void setWidthToFontSize(float widthToFontSize) {
		this.widthToFontSize = widthToFontSize;
	}
	
	void show() {
		avatarSelectionPresenter.bindDisplay(avatarSelectionView);
		display.getToolbar().add(avatarSelectionView);

		stages = DemoData.getDemoStages();

		int column = 0;
		int NUM_ROWS = 11;
		for (Stage stage : stages) {
			Label stageHeader = new Label(stage.getName());
			stageHeader.setStylePrimaryName("kanbanStageHeader");
			table.setCellPadding(0);
			table.setCellSpacing(0);
			table.setStyleName("kanbanTable");
			table.setWidget(0, column, stageHeader);
			Element elem = table.getColumnFormatter().getElement(column);
			if (column == 0) {
				elem.addClassName("first");
			}
			if (stage.isBuffer()) {
				elem.addClassName("buffer");
			}
			for (int i = 1; i <= NUM_ROWS; i++) {
				final StageView stageColumn = new StageView(stage);
				AbsolutePositionDropController dropController = new StageDropController(
						stageColumn, taskService);
				dragController.registerDropController(dropController);
				stageColumn.setStylePrimaryName("kanbanStageColumn");
				if (column == 0) {
					stageColumn.addStyleName("first");
				}
				if (stage.isBuffer()) {
					stageColumn.addStyleName("buffer");
				}
				table.setWidget(i, column, stageColumn);
			}
			column++;
		}
		display.getPanel().add(table);

		// Busy indicator
		busyIndicatorPresenter.bindDisplay(busyIndicatorView);
		display.getPanel().add(busyIndicatorView);

		// Get data
		findAllUserStories();
		findAllTasks();
	}
}
